﻿package meps.net {
	
	import flash.events.AsyncErrorEvent;
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.events.SecurityErrorEvent;
	import flash.events.StatusEvent;
	import flash.net.LocalConnection;

	/*
	 * TODO
	 * - работать на одном localconnection с последовательными вызовами на нем close/connect;
	 * - добавить пингование экземпляров соединения для автоматического обнаружения утраты одной из точек (+обработка AsyncError?);
	 * - добавить событие при вызове отсутсвующего метода с передачей вызванных параметров в обработчик;
	 * - метод для получения списка доступных для удаленного вызова методов (и их описаний?);
	 * - реализовать сериализацию и передачу данных размером более 40кб, с десериализацией и сборкой данных получателем;
	 * - реализовать объединение в группы более двух точек, вызов методов как по айдишнику точки, так и броадкастовый, управление отключением клиентов, залочивание и разлочивание групп и т.д.;
	 * - для группы реализовать подсчет числа локальных экземпляров с возможностью раздельного управления каждым;
	 *
	 * Для возможности связывать несколько клиентов в группы, последовательность действий:
	 * - если удалось занять канал сервера, то создает собственного клиента и регистрирует сам себя;
	 * - для клиента создать канал на прослушивание и послать серверу команду на регистрацию себя по собственному уникальному каналу;
	 * - на каждую регистрацию сервер рассылает по всем доступным клиентам их идентификаторы и полный список готовых к прослушиванию клиентов;
	 * - по окончанию регистрации всех клиентов сервер рассылает имена каналов по всем клиентам;
	 * Что будет при закрывании сервера до или после объединения клиентов в группу?
	 */
	/**
	 * Универсальный локальный RPC мост, AS3
	 *
	 * <p>Существенные отличия от использованного в качестве прототипа SWFBridge
	 * (<a href="http://gskinner.com/" target="_blank">gskinner.com</a>):<ul>
	 *
	 * <li>после установления двустороннего соединения по заданному базовому
	 * идентификатору, оно автоматически переносится на соединение по
	 * уникальному сгенерированному идентификатору;</li>
	 *
	 * <li>от вызова метода старта соединения до момента фактического
	 * установления этого соединения все удаленные вызовы буферизуются и в
	 * дальнейшем гарантированно отправляются в том же порядке;</li>
	 *
	 * <li>возможность многократного завершения и повторного установления
	 * соединения посредством одного экземпляра моста без необходимости
	 * создавать новые;</li>
	 *
	 * <li>вызов метода завершения соединения автоматически закрывает его и на
	 * втором клиенте;</li>
	 *
	 * <li>большая устойчивость к передаваемым параметрам, а также к
	 * неправильной последовательности вызовов методов установления соединения,
	 * удаленного вызова и завершения соединения;</li>
	 *
	 * <li>возможность работы как в собственном (отнаследованном) клиентском
	 * объекте, так и во внешнем;</p>
	 *
	 * </ul></p>
	 *
	 * @version	3.3
	 * @author	meps
	 */
	public class CLocalBridge extends EventDispatcher implements ILocalBridge {
		
		public static const CONNECT:String = "connect"; // событие установления соединения
		public static const CALL:String = "call"; // событие удаленного вызова
		public static const CLOSE:String = "close"; // событие разрыва соединения
		
		private static const STATE_NONE:int = 0;
		private static const STATE_SERVER:int = 1; // ожидание сервером соединения с клиентами
		private static const STATE_CLIENT:int = 2; // ожидание клиентом команды инициализации после его регистрации на сервере
		private static const STATE_CONNECT:int = 3; // состояние полноценного двустороннего соединения
		private static const STATE_CLOSE:int = 4;
		
		private static const METHOD_CONNECT:String = "__connect";
		private static const METHOD_CALL:String = "__call";
		private static const METHOD_CLOSE:String = "__close";
		private static const METHOD_RETAIN:String = "__retain";
		
		///////////////////////////////////////////////////////////////////////
		// INSTANCE
		
		private var _state:int; // текущее состояние
		private var _target:Object; // клиентский объект для вызова методов
		private var _name:String; // имя удаленного экземпляра
		private var _buffer:Array /*Array*/; // исходящий буфер, накапливающий команды от вызова установки соединения, до фактического подтверждения об установлении соединения
		private var _lc:LocalConnection;
		
		/**
		 * Конструктор.
		 */
		public function CLocalBridge(target:Object = null) {
			//trace("3_CLocalBridge.constructor()");
			_state = STATE_NONE;
			_target = target ? target : this;
			_buffer = [ ];
			
			_lc = new LocalConnection();
			var o:Object = { };
			o[METHOD_CONNECT] = doConnect;
			o[METHOD_CALL] = doCall;
			o[METHOD_CLOSE] = doClose;
			_lc.client = o;
			_lc.allowDomain("*");
			_lc.addEventListener(StatusEvent.STATUS, doFormal);
			_lc.addEventListener(SecurityErrorEvent.SECURITY_ERROR, doFormal);
			_lc.addEventListener(AsyncErrorEvent.ASYNC_ERROR, doFormal);
		}
		
		/**
		 * @inheritDoc
		 */
		public function get target():Object {
			return _target;
		}
		
		/**
		 * @inheritDoc
		 */
		public function set target(value:Object):void {
			_target = value ? value : this;
		}
		
		/**
		 * @inheritDoc
		 */
		public function get available():Boolean {
			return _state == STATE_CONNECT;
		}
		
		/**
		 * @inheritDoc
		 */
		public function connect(name:String):Boolean {
			//trace("3_CLocalBridge.connect(" + name + ")");
			if (name)
				name = name.split(":").join("");
			if (!name || (_state != STATE_NONE && _state != STATE_CLOSE))
				return false;
			
			_state = STATE_SERVER;
			_buffer.length = 0; // буфер принудительно очищается при каждом новом соединении
			
			try {
				// попытка стать сервером
				_lc.connect(name);
			} catch (error:ArgumentError) {
				// сервер уже существует
				_state = STATE_CLIENT;
				var n:String = uniqueID();
				//trace("3_CLocalBridge.client id:" + n);
				_lc.connect(n);
				_lc.send(name, METHOD_CONNECT, n);
			}
			return true;
		}
		
		/**
		 * @inheritDoc
		 */
		public function call(method:String, ... params:Array /*Object*/):Boolean {
			//trace("3_CLocalBridge.call(" + method + ", " + params + ")");
			params.unshift(method);
			
			if (_state == STATE_CONNECT) {
				params.unshift(_name, METHOD_CALL);
				//trace("3_CLocalBridge.send args:" + params);
				_lc.send.apply(_lc, params);
			} else if (_state == STATE_SERVER || _state == STATE_CLIENT) {
				//trace("3_CLocalBridge.store args:" + params);
				_buffer.push(params);
			}
			
			return true;
		}
		
		/**
		 * @inheritDoc
		 */
		public function close():Boolean {
			//trace("3_CLocalBridge.close()");
			if (_state == STATE_CLOSE)
				return false;
			
			if (_state == STATE_CONNECT) {
				// разорвать двустороннее соединение на втором получателе
				_lc.send(_name, METHOD_CLOSE);
				_lc.close();
			} else if (_state == STATE_SERVER || _state == STATE_CLIENT) {
				_lc.close();
			}
			
			_state = STATE_CLOSE;
			return true;
		}
		
		/**
		 * @inheritDoc
		 */
		override public function toString():String {
			return "[CLocalBridge state:" + _state +
				", name:" + _name +
				"]";
		}
		
		///////////////////////////////////////////////////////////////////////
		// PRIVATE
		
		// обработчик удаленной инициализации соединения
		private function doConnect(name:String):void {
			//trace("3_CLocalBridge.onConnect id:" + name);
			if (_state == STATE_SERVER) {
				// клиент соединился с сервером
				var n:String = uniqueID();
				_lc.close();
				_lc.connect(n);
				_lc.send(name, METHOD_CONNECT, n);
			}
			
			_name = name;
			_state = STATE_CONNECT;
			dispatchEvent(new Event(CONNECT));
			
			// отправить очередь исходящих команд
			while (_buffer.length > 0) {
				var a:Array = _buffer.shift();
				a.unshift(_name, METHOD_CALL);
				//trace("3_CLocalBridge.resend args:" + a);
				_lc.send.apply(_lc, a);
			}
		}
		
		// обработчик удаленных вызовов
		private function doCall(method:String, ... args:Array /*Object*/):void {
			//trace("3_CLocalBridge.onCall(" + method + ", " + args + ")");
			var fn:Function = _target[method];
			if (fn == null)
				return;
			
			fn.apply(_target, args);
			dispatchEvent(new Event(CALL));
		}
		
		private function doClose():void {
			//trace("3_CLocalBridge.onClose()");
			if (_state == STATE_CLOSE)
				return;
			
			if (_state == STATE_SERVER ||
				_state == STATE_CLIENT ||
				_state == STATE_CONNECT)
				_lc.close();
			
			_state == STATE_CLOSE;
			dispatchEvent(new Event(CLOSE));
		}
		
		// формальный обработчик
		private function doFormal(event:Event):void {
		}
		
		private function uniqueID():String {
			return "_" + (new Date()).valueOf().toString(16) + int(Math.random() * int.MAX_VALUE).toString(16);
		}
		
	}
	
}
